package top.biangege.bbs.aop;


import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import top.biangege.bbs.aop.annotation.RedisCache;
import top.biangege.bbs.utils.RedisUtil;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Component
@Aspect
public class RedisAspect {

    @Resource
    private RedisUtil redisUtil;

    private static Logger log = LoggerFactory.getLogger(RedisAspect.class);

    @Pointcut("execution(* top.biangege.bbs.service.impl.*.*(..))")
    public void pointCut() {

    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        try {
            // 获取 当前代理的目标类
            Class c = joinPoint.getSignature().getDeclaringType();
            // 获取 当前代理的目标方法（织入的方法）
            Method method = null;
            // 遍历 当前代理的目标类中的所有方法
            for (Method m : c.getDeclaredMethods()) {
                // 判断 当前遍历到的方法的名称 是否和 当前被代理的目标方法的名称  相同
                if (m.getName().equals(joinPoint.getSignature().getName())) {
                    // 当前遍历到的方法 就是 当前被代理的目标方法
                    method = m;
                    break;
                }
            }
            // 判断 当前代理的目标方法 是否需要启用 Redis缓存策略（ 判断 当前代理的目标方法 是否含有 @RedisCache注解 ）
            if (method.getDeclaredAnnotation(RedisCache.class) != null) {
                // 当前代理的目标方法 含有 @RedisCache注解 => 开启 Redis 缓存策略
                log.info("启用 Redis 缓存策略");
                // 为当前代理的目标方法 生成 缓存的 key（生成规则：方法的签名 + 实参数据）
                Map<String, Object> map = new HashMap<>();
                map.put("signature", c.getName() + "." + method.getName());    // 载荷 方法的签名
                map.put("params", joinPoint.getArgs());                          // 载荷 实参列表
                String key = JSON.toJSONString(map);                          // 序列化 为 JSON 字符串 作为 缓存的key

                // 循环
                while (true) {
                    // 判断 Redis 缓存中 是否存在 该 key 的缓存数据
                    if (redisUtil.hasKey(key)) {
                        // 该 key 的缓存数据存在
                        log.info(" Redis 缓存命中");
                        // 直接返回 Redis中的缓存数据
                        Object value = redisUtil.get(key);
                        return value.equals("null") ? null : value;
                    } else {
                        // 该 key 的缓存数据不存在
                        log.info("Redis 缓存不存在");
                        // 争夺分布式锁
                        if (redisUtil.setnx("mutex-" + key, 10000)) {
                            // 当前线程争夺到了分布式锁 => 查询数据源 更新缓存
                            log.info("当前线程争夺到了分布式锁 => 查询数据源 更新缓存");
                            // 调用核心业务方法（直接执行代理的目标方法）
                            Object value = joinPoint.proceed();
                            // 获取 当前代理的目标方法 缓存策略的缓存时间
                            long duration =
                                    method.getDeclaredAnnotation(RedisCache.class).duration() +     // 基底时间
                                            (int) (Math.random() * 10);                                      // 随机时间（为了防止缓存雪崩）
                            // 判断 从数据源查询到的数据是否为 null
                            if (value != null) {
                                // 该数据存在 正常生成缓存
                                redisUtil.set(key, value, duration);
                            } else {
                                // 该数据不存在 要转换value值，依然生成缓存 有效时间可以短一点（防止 缓存穿透）
                                redisUtil.set(key, "null", 10);
                            }
                            // 返回 数据源中查询到的数据
                            return value;
                        } else {
                            // 当前线程没有争夺到分布式锁  等待缓存更新后重新读取缓存
                            Thread.currentThread().sleep(500);
                        }
                    }
                }
            } else {
                // 当前代理的目标方法 不含有 @RedisCache注解 => 无缓存策略
                log.info("无缓存策略");
                // 调用核心业务方法（直接执行代理的目标方法）
                Object value = joinPoint.proceed();
                return value;
            }
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
